/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.loaders; import org.openide.TopManager; import org.openide.nodes.*; import org.openide.cookies.FilterCookie; import org.openide.cookies.ElementCookie; import org.openide.cookies.SourceCookie; import org.openide.src.SourceElement; import org.openide.util.WeakListener; import org.openide.util.RequestProcessor; import java.util.Collection; import java.util.Collections; import java.util.Hashtable; import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedList; import java.util.Iterator; import java.util.HashSet; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; /** * Supports working with a set of filters defined by data objects. * Provides a keyed list of children * taken from a {@link DataFolder}. * <p>An instance of this filter in its default settings * accepts all {@link DataObject}s. * <p> * Additional specific filters can be added to this filter with {@link #putFilter} * to filter particular kinds of data objects. * <P> * Also it is possible to registering filter editor classes and to * obtain them, so as to allow modifications to the filter by the user. * <p> * The effect of this filter is that: * <ul> * <li>By default, all data objects in the folder will be "keys" * (i.e. grouping categories). * <li>Particular types of data objects can be suppressed as keys * (and corresponding children), according to the loader's data object * representation class. * <li>Data objects not providing {@link ElementCookie} will be shown as the * sole child for their key. * <li>Data objects providing <code>ElementCookie</code> will not be shown * directly; rather their "elements parent" will be consulted. By default * all the children it provides will be spliced into the children list * keyed by the data object. * <li><code>ElementCookie</code>-enabled children will be filtered * before being used as the children for a key; the filter used will be applied * to a copy of the elements parent node, so that it may handle the filter * logic. Filters may be added according to the type of the filter, which * is specified by a filter representation class. * </ul> */ public class DataObjectFilter extends Children.Keys { // static .......................................................................... /** represen. class (DO) => filter class * @associates Class*/ private static Hashtable doToFilter = new Hashtable (); /** Personal request processor */ private static RequestProcessor processor = new RequestProcessor (); /** Register a new filter type for a given type of data object. * @param representationClass the designated super class of all data objects that can use * this filter * @param filterClass the class of a filter to use for such data objects * @see FilterCookie#getFilterClass */ public static void registerFilterClass ( Class representationClass, Class filterClass ) { doToFilter.put (representationClass, filterClass); } /** Get the filter class currently registered for a data object representation class. * @return the proper filter class, or <code>null</code> */ public static Class getFilterClass (Class representationClass) { return (Class) doToFilter.get (representationClass); } // variables ...................................................................... /** represen. class (DO) => represen. class (DO) * @associates Class*/ private Hashtable acceptedDOs = new Hashtable (); /** filter class => filter * @associates Object*/ private Hashtable filters = new Hashtable (); /** current representing DF */ private DataFolder dataFolder; /** Filters DOs */ private DataFilter filter; /** Listens on DataFolder */ private PropertyChangeListener folderFerret; /** Keeps listener */ private PropertyChangeListener folderFerretKeeper; /** Listens Nodes */ private NodeListener ferret; /** Node => DO * @associates Object*/ private Hashtable noToDo = new Hashtable (); /** DO => Node */ // private Hashtable doToNo = new Hashtable (); /** true if subnodes of this node are visible */ private boolean nodesInited = false; // init ........................................................................... /** Create a new filter which will accept given set of data objects. * Initially unattached to any data folder. * @param representationClasses representation classes of data objects to be shown */ public DataObjectFilter (Class[] representationClasses) { filter = new DataFilter () { public boolean acceptDataObject (DataObject obj) { Enumeration e = acceptedDOs.keys (); Class c = obj.getClass (); while (e.hasMoreElements ()) { if (((Class) e.nextElement ()).isAssignableFrom (c)) { return true; } } return false; } }; folderFerret = WeakListener.propertyChange ( folderFerretKeeper = new PropertyChangeListener () { public void propertyChange (PropertyChangeEvent ev) { if (nodesInited) { if (ev.getPropertyName().equals(DataFolder.PROP_CHILDREN)) { refreshAll (); } } } }, null); ferret = new NodeAdapter () { public void childrenAdded (NodeMemberEvent ev) { refresh ((DataObject) noToDo.get (ev.getSource ())); } public void childrenRemoved (NodeMemberEvent ev) { refresh ((DataObject) noToDo.get (ev.getSource ())); } }; int i, k = representationClasses.length; for (i = 0; i < k; i++) acceptedDOs.put (representationClasses [i], representationClasses [i]); //System.out.println ("#DOF: " + (++dof)); // NOI18N } //static int dof = 0; //static int refreshAllCounter = 0; //static int createNodesCount = 0; // protected void finalize () { TESTING //System.out.println ("#DOF end: " + (--dof)); // NOI18N // } /** Create a new filter which will accept all data objects. * Initially unattached to any data folder. */ public DataObjectFilter () { this (new Class [] {}); DataLoader[] loaders = TopManager.getDefault ().getLoaderPool ().toArray (); int i, k = loaders.length; for (i = 0; i < k; i++) { Class c = loaders [i].getRepresentationClass (); if (DataFolder.class.isAssignableFrom (c)) continue; acceptedDOs.put (c, c); } } /** Create a new filter which will accept all data objects in a given folder. * @param dataFolder the folder to filter */ public DataObjectFilter (DataFolder dataFolder) { this (); this.dataFolder = dataFolder; } // main methods .................................................................. /** * Add a filter for a certain type of data object. * The previous filter, if any, will be removed. * @param filterClass the representation class this filter belongs to * @param filter the filter to use for all data objects requesting this type of filter, * via {@link FilterCookie#getFilterClass}. May be <code>null</code> to remove. */ public void putFilter (Class filterClass, Object filter) { //System.out.println ("putFilter: "); // NOI18N Object old = filters.get (filterClass); if (old == null) { if (filter == null) return; } else if ((filter != null) && filter.equals (old)) return; if (filter == null) filters.remove (filterClass); else filters.put (filterClass, filter); if (nodesInited) refreshNodes (); //System.out.println ("putFilter end: "); // NOI18N } /** * Permit a representation class of data object to be shown * (after appropriate filtering). * By default all are shown, so this need be used only to counteract * {@link #removeLoader}. * @param representationClass the data object representation class */ public void addLoader (Class representationClass) { acceptedDOs.put (representationClass, representationClass); if (nodesInited) refreshAll (); } /** * Prevent a representation class of data object from being show at all. * @param representationClass the data object representation class * @see DataLoader#getRepresentationClass */ public void removeLoader (Class representationClass) { Object filter = doToFilter.get (representationClass); if (filter != null) filters.remove (filter); acceptedDOs.remove (representationClass); if (nodesInited) refreshAll (); } /** Attach the support to a different folder. * @param f the new folder */ public synchronized void setDataFolder (DataFolder f) { if ((dataFolder != null) && (f != null) && dataFolder.equals (f) ) return; if (dataFolder != null) { dataFolder.removePropertyChangeListener (folderFerret); cancel (); } dataFolder = f; //System.out.println ("DOF.setDataFolder: " + ((dataFolder == null) ? "null" : "" + dataFolder.getPrimaryFile ()) + " " + nodesInited); // NOI18N // if (nodesInited) refreshAll (); if (dataFolder != null) dataFolder.addPropertyChangeListener (folderFerret); } /** Get the folder this support is attached to. * @return the folder */ public DataFolder getDataFolder () { return dataFolder; } // children implementation ......................................................... /* Overrides initNodes to run the preparation task of the * data object filter, call refreshKeys and start to * listen to the changes in the element too. */ protected void addNotify () { // setKeysHelper (Collections.EMPTY_SET); // refreshAll (); nodesInited = true; //System.out.println ("DOF.addNotify"); // NOI18N } protected void removeNotify () { setKeysHelper (Collections.EMPTY_SET); nodesInited = false; //System.out.println ("DOF.removeNotify"); // NOI18N } /** Create children for a data-object key. * If {@link ElementCookie} is provided, then the proxy node's children * are used for this node's children (for this key), after possible filtering * based on the {@link #putFilter current filters}. * If <code>ElementCookie</code> is not provided, then * (a copy of) this data's object's delegate node is used as the sole child * for this key. * @param key a {@link DataObject} to create representative children for * @return a list of child nodes for this key */ protected Node[] createNodes (Object key) { DataObject DO = (DataObject) key; if (!currentKeys.contains (DO)) { //System.out.println (" createNodes !!!!!!!!!!!! " + DO.getPrimaryFile ()); // NOI18N return new Node[] {}; } //System.out.println (" createNodes: " + (++createNodesCount) + " " + DO.getPrimaryFile ()); // NOI18N ElementCookie ec = (ElementCookie) DO.getCookie (ElementCookie.class); Node root; if (ec == null) { root = DO.getNodeDelegate ().cloneNode (); //System.out.println (" createNodes end: " + (--createNodesCount) + " " + DO.getPrimaryFile ()); // NOI18N return new Node[] {root}; } root = ec.getElementsParent (); FilterCookie fc = (FilterCookie) root.getCookie (FilterCookie.class); if (fc != null) { Object filter = filters.get (fc.getFilterClass ()); if (filter != null) fc.setFilter (filter); } //System.out.println (" createNodes1 "); // NOI18N Node[] n = root.getChildren ().getNodes (); // Connect to node... root.addNodeListener (ferret); noToDo.put (root, key); // doToNo.put (key, root); int i, k = n.length; Node[] nn = new Node [k]; for (i = 0; i < k; i++) nn [i] = n [i].cloneNode (); //System.out.println (" createNodes end: " + (--createNodesCount) + " " + DO.getPrimaryFile ()); // NOI18N return nn; } // other methods ................................................................... /** Helper accessing method. * Collection getKeys () { return keys; } */ Collection keys; /** Refreshs all current keys. */ void refreshAllKeys () { setKeys (Collections.EMPTY_SET); setKeys (keys); } /** Helper accessing method. */ void setKeysHelper (Collection l) { setKeys (keys = l); } /** Helper accessing method. */ void refreskKeyHelper (Object l) { refreshKey (l); } private synchronized void cancel () { currentTask = null; //System.out.println ("cancel: "); // NOI18N Enumeration e = noToDo.keys (); while (e.hasMoreElements ()) { Node n = (Node) e.nextElement (); n.removeNodeListener (ferret); } noToDo = new Hashtable (); // doToNo = new Hashtable (); //System.out.println ("cancel end: "); // NOI18N } private Runnable currentTask; private HashSet currentKeys; /** * Refreshs all dataobjects from current folder. */ private void refreshAll () { if (dataFolder == null) { setKeysHelper (Collections.EMPTY_SET); return; } //final DataFolder df = dataFolder; currentKeys = new HashSet (); //System.out.println ("refreshAll prepare: " + df.getPrimaryFile () + " by " + Thread.currentThread ().getName ()); // NOI18N processor.postRequest (currentTask = new Runnable () { public void run () { if (currentTask != this) { //System.out.println ("refreshAll !!!!!!" + " " + df.getPrimaryFile ()); // NOI18N return; } //System.out.println ("refreshAll: " + (++refreshAllCounter) + " " + df.getPrimaryFile ()); // NOI18N ArrayList newDo = dataFolder.getChildrenList (); int i; for (i = newDo.size () - 1; i >= 0; i--) if (!filter.acceptDataObject ((DataObject) newDo.get (i))) newDo.remove (i); // LinkedList ll = new LinkedList (getKeys ()); if (currentTask != this) { //System.out.println ("refreshAll2 !!!!!!" + " " + df.getPrimaryFile ()); // NOI18N return; } //System.out.println ("PARSING " + df.getPrimaryFile () + " ........................................................................."); // NOI18N currentKeys = new HashSet (newDo); LinkedList ll = new LinkedList (); int k = newDo.size (); for (i = 0; i < k; i++) { DataObject o = (DataObject) newDo.get (i); SourceCookie sc = (SourceCookie) o.getCookie (SourceCookie.class); if (sc != null) { SourceElement se = sc.getSource (); se.prepare ().waitFinished (); if (currentTask != this) { //System.out.println ("refreshAll3 !!!!!!" + " " + df.getPrimaryFile ()); // NOI18N return; } } ll.add (o); setKeysHelper (ll); /* if (!getKeys ().contains (o)) { // Disconnect from node... Node n = (Node) doToNo.remove (o); if (n == null) continue; noToDo.remove (n); n.removeNodeListener (ferret); } */ } //System.out.println ("refreshAll end: " + (--refreshAllCounter) + " " + df.getPrimaryFile ()); // NOI18N } }); } /** * Refreshs all dataobjects from current folder. */ private void refreshNodes () { if (dataFolder == null) { setKeysHelper (Collections.EMPTY_SET); return; } processor.postRequest (new Runnable () { public void run () { //System.out.println (" refreshNodes: "); // NOI18N if (dataFolder == null) return; /* JST: Not necessary anymore Collection c = getKeys (); setKeysHelper (Collections.EMPTY_SET); setKeysHelper (c); */ refreshAllKeys (); //System.out.println (" refreshNodes end: "); // NOI18N } }); } /** * Refreshs given dataobject from current folder. */ private void refresh (final DataObject key) { if (!currentKeys.contains (key)) { //System.out.println (" refresh key prepare !!!!!: " + key.getPrimaryFile ()); // NOI18N return; } if (!filter.acceptDataObject (key)) return; //System.out.println (" refresh key prepare: " + key.getPrimaryFile () + " by " + Thread.currentThread ().getName ()); // NOI18N processor.postRequest (new Runnable () { public void run () { if (!currentKeys.contains (key)) { //System.out.println (" refresh key !!!!!!: " + key.getPrimaryFile ()); // NOI18N return; } //System.out.println (" refresh key: " + key.getPrimaryFile ()); // NOI18N refreskKeyHelper (key); } }); } } /* * Log * 20 src-jtulach1.19 1/13/00 Ian Formanek NOI18N * 19 src-jtulach1.18 12/15/99 Jan Jancura Bug 4013 * 18 src-jtulach1.17 12/2/99 Jaroslav Tulach Refresh of content of * folder is now done in special request processor * 17 src-jtulach1.16 11/5/99 Jaroslav Tulach WeakListener has now * registration methods. * 16 src-jtulach1.15 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 15 src-jtulach1.14 9/10/99 Jaroslav Tulach Children.Keys has keys * variable no more. * 14 src-jtulach1.13 8/27/99 Jaroslav Tulach New threading model & * Children. * 13 src-jtulach1.12 8/10/99 Ales Novak property children fired * only iff children has changed * 12 src-jtulach1.11 8/9/99 Jan Jancura * 11 src-jtulach1.10 8/5/99 Jan Jancura * 10 src-jtulach1.9 8/5/99 Jan Jancura * 9 src-jtulach1.8 7/16/99 Jan Jancura Optimalization & * filtering improved. * 8 src-jtulach1.7 7/1/99 Jan Jancura Support for filtering * improved * 7 src-jtulach1.6 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 6 src-jtulach1.5 4/22/99 Jaroslav Tulach Does not clone root of * getElementParent. * 5 src-jtulach1.4 4/21/99 Jaroslav Tulach * 4 src-jtulach1.3 4/21/99 Jan Jancura * 3 src-jtulach1.2 4/16/99 Jan Jancura * 2 src-jtulach1.1 4/2/99 Jesse Glick [JavaDoc] * 1 src-jtulach1.0 4/2/99 Jan Jancura * $ */